iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0

今天跟大家分享如何通過昨天寫的測試。

原始碼可以參考 D09/component-test

https://ithelp.ithome.com.tw/upload/images/20230925/20158615d8hNkQuYmr.png

再次回顧整個工作流程,我們在 D06 - 測試驅動開發步驟2 完成了,現在的目標是完成 步驟3步驟4

新增 Atom

使用前請先安裝 jotai

npm i jotai

https://ithelp.ithome.com.tw/upload/images/20230925/20158615BKAQLIz26d.png

textInput.ts 新增初始值與 Atom

import atom from 'jotai'

// 1. 建立 textInput 初始值
export const initialCourseName = Data.tagged<InitialTextInput>(
  'InitialTextInput'
)({ value: '' })

// 2. 新增 courseNameAtom,設定 atom 的初始值為 initialCourseName
export const courseNameAtom = atom<TextInput>(initialCourseName)

接著在測試的檔案 import 初始值與 Atom

import {
  InitialTextInput,
  courseNameAtom,
} from '../../data/addCourseForm/textInput'

完成以上動作後存檔,就可以看到前面三個不顯示錯誤訊息的測試已經通過了

https://ithelp.ithome.com.tw/upload/images/20230925/201586150v772TzkNE.png

新增錯誤訊息元件

接下來為了完成 步驟4,我們需要新增一個錯誤訊息元件

https://ithelp.ithome.com.tw/upload/images/20230925/20158615gnrVHxeQ1U.png

由於後面的 DateRange 或是 Description 欄位可能也會用到錯誤訊息元件,所以我們把它放在 common 資料夾下面。

import { Match, pipe } from 'effect'
import { FC } from 'react'
import {
  InvalidTextInput,
  TextInput,
} from '../../../data/addCourseForm/textInput'

// 把顯示元件和邏輯元件分開,比較好讀

// 顯示元件
const Displayed: FC<InvalidTextInput> = ({ reason }) => (
  <p className={'error-message h-5 text-sm text-red-600 dark:text-red-500'}>
    {reason}
  </p>
)

// 邏輯元件
export const ErrorMessage: FC<TextInput> = (input) =>
  pipe(
    Match.value(input),
    Match.tag('InvalidTextInput', (input) => <Displayed {...input} />),
    Match.tag('InitialTextInput', (input) => <></>),
    Match.tag('ValidTextInput', (input) => <></>),
    Match.exhaustive
  )

ErrorMessage 其實還隱含了根據同的 tag 決定是否顯示的邏輯,因此我們在撰寫時把它拆成兩部分,一部分負責顯示元件,例如設定 css 屬性,另外一部分則是做邏輯判斷。

修改 CourseName.tsx

import { flow } from 'effect/Function'
import { useAtom } from 'jotai'
import { courseNameAtom } from '../../data/addCourseForm/textInput'
import { ErrorMessage } from './common/ErrorMessage'

// 1. 設定上下限
const limit = { minLen: 1, maxLen: 50 }

export const CourseName = () => {
  // 2. 從 atom 取 value 跟 set function
  const [textInput, setTextInput] = useAtom(courseNameAtom)

  return (
    <div>
      <label
        data-for="course-name"
        className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
      >
        Course Name
      </label>
      <input
        // 3. 設定受控的 input value
        value={textInput.value} 
        // 4. 設定 onChange 處理函式
        onChange={flow(textInputOfEvent(limit), setTextInput)}
        // 5. 設定 onBlur 處理函式
        onBlur={flow(textInputOfEvent(limit), setTextInput)}
        type="text"
        id="course-name"
        placeholder="Enter course name"
        className="... 忽略"
      />
      <!-- 6. 使用上面寫的 ErrorMessage 元件 -->
      <ErrorMessage {...textInput} />
    </div>
  )
}

修改 textInput.tsx

https://ithelp.ithome.com.tw/upload/images/20230925/201586154aM8cfEsBb.png

// 新增一個 constructor
export const textInputOfEvent =
  (limit: TextInputLimit) =>
  (event: ChangeEvent<HTMLInputElement>): InvalidTextInput | ValidTextInput =>
    textInputOf(limit)(event.target.value)

完成以上步驟就可以看到一整排綠色勾勾

https://ithelp.ithome.com.tw/upload/images/20230925/20158615CIik4MxkUR.png

抽出通用元件

由於 labelinput 將會被表單重複利用,因此我們把它抽出來放到 \common 裡面

https://ithelp.ithome.com.tw/upload/images/20230925/20158615ttZUMwZpto.png

import { flow } from 'effect/Function'
import { useAtom } from 'jotai'
import {
  courseNameAtom,
  textInputOfEvent,
} from '../../data/addCourseForm/textInput'
import { ErrorMessage } from './common/ErrorMessage'
import { Label } from './common/Label'
import { TextInput } from './common/TextInput'

const limit = { minLen: 1, maxLen: 50 }

export const CourseName = () => {
  const [courseName, setCourseName] = useAtom(courseNameAtom)

  return (
    <div>
      <Label htmlFor="course-name">Course Name</Label>
      <TextInput
        value={courseName.value}
        onChange={flow(textInputOfEvent(limit), setCourseName)}
        onBlur={flow(textInputOfEvent(limit), setCourseName)}
        id="course-name"
        placeholder="Enter course name"
      />
      <ErrorMessage {...courseName} />
    </div>
  )
}

因為我們已經完成了測試,所以重構時會顯得特別的安心,只要最後測資都有過就行了。


明天來說明這些程式碼用到的 FP 技巧


上一篇
D09 - 開始元件測試
下一篇
D11 - 淺談型別安全
系列文
從 Next.js 開始的 Functional Programming30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言